﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using Microsoft.SqlServer.Types;
using System.Text;
using System.Diagnostics;
using System.Data.SqlTypes;

namespace DataConDemoWebRole.Business
{
    /// <summary>
    /// These conversion methods use GML, otherwise known as Geographical XML.
    /// Although not used in this project, they illustrate techniques that may be useful
    /// in the construction of geographies, as Linq to XML is sometimes simpler to use
    /// than the SqlGeographyBuilder.
    /// </summary>
    public static class GmlHelpers
    {
        public static readonly XNamespace GmlNamespace = "http://www.opengis.net/gml";
        public static readonly int SRID = 4326;

        /// <summary>
        /// Parse GML for a MultiPolygon.
        /// </summary>
        /// <remarks>Only exterior polygons are included.</remarks>
        /// <param name="sxml">GML in string form. Might be either a Polygon or MultiSurface</param>
        /// <returns></returns>
        public static Models.GeoJson.MultiPolygon GeoJsonMultiPolygonFromGml(string sxml)
        {
            var xdoc = XDocument.Parse(sxml);
            var xns = xdoc.Root.Name.Namespace;

            if (xdoc.Root.Name.LocalName == "Polygon")
            {
                var pos = xdoc.Root
                    .Element(xns + "exterior")
                    .Element(xns + "LinearRing")
                    .Element(xns + "posList");
                var pieces = ((string)pos).Split(' ');
                var points = new List<double[]>();
                for (int i = 0; i < pieces.Length; i += 2)
                {
                    var point = new double[2];
                    point[0] = Double.Parse(pieces[i]);
                    point[1] = Double.Parse(pieces[i + 1]);
                    points.Add(point);
                }

                var mpoly = new Models.GeoJson.MultiPolygon()
                {
                    coordinates = new double[1][][][]
                };
                mpoly.coordinates[0] = new double[1][][];
                mpoly.coordinates[0][0] = points.ToArray();
                return mpoly;
            }

            if (xdoc.Root.Name.LocalName == "MultiSurface")
            {
                var polycoords = new List<double[][][]>();
                foreach (var poly in xdoc.Root.Element(xns + "surfaceMembers").Elements(xns + "Polygon"))
                {
                    var pos = poly
                        .Element(xns + "exterior")
                        .Element(xns + "LinearRing")
                        .Element(xns + "posList");
                    var pieces = ((string)pos).Split(' ');
                    var points = new List<double[]>();
                    for (int i = 0; i < pieces.Length; i += 2)
                    {
                        var point = new double[2];
                        point[0] = Double.Parse(pieces[i]);
                        point[1] = Double.Parse(pieces[i + 1]);
                        points.Add(point);
                    }
                    var epoly = new double[1][][];
                    epoly[0] = points.ToArray();
                    polycoords.Add(epoly);
                }

                var mpoly = new Models.GeoJson.MultiPolygon()
                {
                    coordinates = polycoords.ToArray()
                };
                return mpoly;
            }

            throw new Exception("GeoJsonMultiPolygonFromGml will only convert MultiSurface and Polygon geometries.");
        }

        /// <summary>
        /// Return the GML for a GeoJson Polygon.
        /// This version only handles a simple polygon with no holes.
        /// </summary>
        /// <remarks>The orientation for a valid geography is counter-clockwise.</remarks>
        /// <param name="poly"></param>
        /// <returns></returns>
        public static string GmlPolygonFromGeoJsonPolygon(Models.GeoJson.Polygon poly)
        {
            var sb = new StringBuilder();

            foreach (var pair in poly.coordinates[0])
            {
                sb.AppendFormat("{0} {1} ", pair[0], pair[1]);
            }

            var xel = new XElement(GmlNamespace + "Polygon",
                new XElement(GmlNamespace + "exterior",
                    new XElement(GmlNamespace + "LinearRing",
                        new XElement(GmlNamespace + "posList", sb.ToString()))));
            return xel.ToString();
        }

        public static string GmlPointFromGeoJsonPoint(Models.GeoJson.Point point)
        {
            var xel = new XElement(GmlNamespace + "Point",
                new XElement(GmlNamespace + "pos", string.Format("{0} {1}", point.coordinates[0], point.coordinates[1])));
            return xel.ToString();
        }

        /// <summary>
        /// Parse GML for a Polygon.
        /// </summary>
        /// <param name="sxml">GML in string form</param>
        /// <returns></returns>
        public static Models.GeoJson.Polygon GeoJsonPolygonFromGml(string sxml)
        {
            var xdoc = XDocument.Parse(sxml);
            var xns = xdoc.Root.Name.Namespace;
            var pos = xdoc.Root
                .Element(xns + "exterior")
                .Element(xns + "LinearRing")
                .Element(xns + "posList");
            var pieces = ((string)pos).Split(' ');
            var points = new List<double[]>();
            for (int i = 0; i < pieces.Length; i += 2)
            {
                var point = new double[2];
                point[0] = Double.Parse(pieces[i]);
                point[1] = Double.Parse(pieces[i + 1]);
                points.Add(point);
            }
            var poly = new Models.GeoJson.Polygon()
            {
                coordinates = new double[1][][]
            };
            poly.coordinates[0] = points.ToArray();
            return poly;
        }


        /// <summary>
        /// Return the SqlGeography for a GeoJson Point.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public static SqlGeography GeographyFromGeoJsonPoint2(Models.GeoJson.Point point)
        {
            var xel = new XElement(GmlNamespace + "Point",
                new XElement(GmlNamespace + "pos", string.Format("{0} {1}", point.coordinates[0], point.coordinates[1])));
            var sx = new SqlXml(xel.CreateReader());
            return SqlGeography.GeomFromGml(sx, SRID);
        }


        /// <summary>
        /// Return the SqlGeography for a GeoJson Polygon.
        /// This version only handles a simple polygon with no holes.
        /// </summary>
        /// <remarks>The orientation for a valid geography is counter-clockwise.</remarks>
        /// <param name="poly"></param>
        /// <returns></returns>
        public static SqlGeography GeographyFromGeoJsonPolygon2(Models.GeoJson.Polygon poly)
        {
            var sb = new StringBuilder();

            foreach (var pair in poly.coordinates[0])
            {
                sb.AppendFormat("{0} {1} ", pair[0], pair[1]);
            }

            var xel = new XElement(GmlNamespace + "Polygon",
                new XElement(GmlNamespace + "exterior",
                    new XElement(GmlNamespace + "LinearRing",
                        new XElement(GmlNamespace + "posList", sb.ToString()))));
            var sx = new SqlXml(xel.CreateReader());
            return SqlGeography.GeomFromGml(sx, SRID);
        }


        /// <summary>
        /// Return the GML for a bounding box.
        /// </summary>
        /// <remarks>The orientation for a valid geography is counter-clockwise.</remarks>
        /// <param name="box"></param>
        /// <returns></returns>
        public static string GmlPolygonFromBoundingBox(Models.BoundingBox box)
        {
            var sb = new StringBuilder();
            sb.AppendFormat("{0} {1} ", box.North, box.West);
            sb.AppendFormat("{0} {1} ", box.South, box.West);
            sb.AppendFormat("{0} {1} ", box.South, box.East);
            sb.AppendFormat("{0} {1} ", box.North, box.East);
            sb.AppendFormat("{0} {1} ", box.North, box.West);
            var xel = new XElement(GmlNamespace + "Polygon",
                new XElement(GmlNamespace + "exterior",
                    new XElement(GmlNamespace + "LinearRing",
                        new XElement(GmlNamespace + "posList", sb.ToString()))));
            return xel.ToString();
        }


        public static SqlGeography GeographyFromBoundingBox2(Models.BoundingBox box)
        {
            var sb = new StringBuilder();
            sb.AppendFormat("{0} {1} ", box.North, box.West);
            sb.AppendFormat("{0} {1} ", box.South, box.West);
            sb.AppendFormat("{0} {1} ", box.South, box.East);
            sb.AppendFormat("{0} {1} ", box.North, box.East);
            sb.AppendFormat("{0} {1} ", box.North, box.West);
            var xel = new XElement(GmlNamespace + "Polygon",
                new XElement(GmlNamespace + "exterior",
                    new XElement(GmlNamespace + "LinearRing",
                        new XElement(GmlNamespace + "posList", sb.ToString()))));
            var sx = new SqlXml(xel.CreateReader());
            return SqlGeography.GeomFromGml(sx, SRID);
        }


        public static SqlGeography GeographyFromGeoJsonLineString2(Models.GeoJson.LineString line)
        {
            var sb = new StringBuilder();

            foreach (var pair in line.coordinates)
            {
                sb.AppendFormat("{0} {1} ", pair[0], pair[1]);
            }
            var xel = new XElement(GmlNamespace + "LineString",
                new XElement(GmlNamespace + "posList", sb.ToString()));
            var sx = new SqlXml(xel.CreateReader());
            return SqlGeography.GeomFromGml(sx, 4326);
        }




        /// <summary>
        /// Parse GML for a Point.
        /// </summary>
        /// <param name="sxml">GML in string form</param>
        /// <returns></returns>
        public static Models.GeoJson.Point GeoJsonPointFromGml(string sxml)
        {
            var xdoc = XDocument.Parse(sxml);
            var xns = xdoc.Root.Name.Namespace;
            var pos = xdoc.Root.Element(xns + "pos");
            var pieces = ((string)pos).Split(' ');
            var point = new Models.GeoJson.Point()
            {
                coordinates = new double[2]
            };
            point.coordinates[0] = Double.Parse(pieces[0]);
            point.coordinates[1] = Double.Parse(pieces[1]);
            return point;
        }


        /// <summary>
        /// Parse GML for a LineString.
        /// </summary>
        /// <param name="sxml">GML in string form</param>
        /// <returns></returns>
        public static Models.GeoJson.LineString GeoJsonLineStringFromGml(string sxml)
        {
            var xdoc = XDocument.Parse(sxml);
            var xns = xdoc.Root.Name.Namespace;
            var pos = xdoc.Root.Element(xns + "posList");
            var pieces = ((string)pos).Split(' ');
            var points = new List<double[]>();
            for (int i = 0; i < pieces.Length; i += 2)
            {
                var point = new double[2];
                point[0] = Double.Parse(pieces[i]);
                point[1] = Double.Parse(pieces[i + 1]);
                points.Add(point);
            }
            return new Models.GeoJson.LineString()
            {
                coordinates = points.ToArray()
            };
        }
    }
}